package com.github.leecho.spring.cloud.gateway.dubbo.client;

import com.github.leecho.spring.cloud.gateway.dubbo.route.DubboRoute;
import com.github.leecho.spring.cloud.gateway.dubbo.route.DubboRoute.DubboProvider;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.spring.ReferenceBean;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.service.GenericService;
import reactor.core.publisher.Mono;

import java.util.Map;
import java.util.concurrent.CompletableFuture;

/**
 * @author LIQIU
 * @date 2021/6/30 14:55
 */
@Slf4j
public class DefaultDubboClient implements DubboClient {

    private final DubboGenericServiceCache dubboGenericServiceCache;

    private final DubboGenericServiceFactory dubboGenericServiceFactory;

    /**
     * 异步执行配置
     */
    private boolean invokeAsync;


    public DefaultDubboClient(DubboGenericServiceCache dubboGenericServiceCache,
                              DubboGenericServiceFactory dubboGenericServiceFactory) {
        this.dubboGenericServiceCache = dubboGenericServiceCache;
        this.dubboGenericServiceFactory = dubboGenericServiceFactory;
    }

    @Override
    public Mono<Object> invoke(DubboRoute route, Map<String, Object> parameters) {
        try {
            if (log.isDebugEnabled()) {
                log.debug("Invoke dubbo service:{} , method:{}, parameters:{}", route.getDubboProvider(), route.getMethod(), parameters);
            }
            GenericService genericService = this.loadGenericService(route.getDubboProvider());
            if (invokeAsync) {
                CompletableFuture<Object> result = genericService.$invokeAsync(route.getMethod(), route.getParameterTypes(), parameters.values().toArray(new Object[]{}));
                if (log.isDebugEnabled()) {
                    log.debug("Async invoke dubbo succeed, service:{} , method:{}, parameters:{}", route.getDubboProvider(), route.getMethod(), parameters);
                }
                return Mono.fromFuture(result);
            } else {
                Object result = genericService.$invoke(route.getMethod(), route.getParameterTypes(), parameters.values().toArray(new Object[]{}));
                if (log.isDebugEnabled()) {
                    log.debug("Invoke dubbo succeed service:{}, method:{}, parameters:{},result:{}", route.getDubboProvider(), route.getMethod(), parameters, result);
                }
                return Mono.just(result);
            }
        } catch (Throwable e) {
            log.error("Invoke dubbo fail:", e);
            throw new RpcException(e.getMessage(), e);
        }
    }

    /**
     * 获取Dubbo泛化调用服务
     *
     * @param dubboProvider
     * @return
     */
    private GenericService loadGenericService(DubboProvider dubboProvider) {
        try {
            GenericService genericService;
            if (this.dubboGenericServiceCache == null) {
                return this.dubboGenericServiceFactory.build(dubboProvider).get();
            }
            ReferenceBean<GenericService> referenceBean = this.dubboGenericServiceCache.get(dubboProvider);
            if (referenceBean == null) {
                synchronized (dubboProvider) {
                    referenceBean = this.dubboGenericServiceCache.get(dubboProvider);
                    if (referenceBean == null) {
                        referenceBean = this.dubboGenericServiceFactory.build(dubboProvider);
                    }
                }
                referenceBean = dubboGenericServiceCache.put(dubboProvider, referenceBean);
            }
            genericService = referenceBean.get();
            return genericService;
        } catch (Exception e) {
            throw new RpcException("load dubbo genericService fail", e);
        }
    }

    public void setInvokeAsync(boolean invokeAsync) {
        this.invokeAsync = invokeAsync;
    }


}
